﻿using Avalonia.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace LayoutViewer.Models.Helpers
{
    public class AdbHelper : IHelper
    {
        public Action<Bitmap, MoveCategory>? Action { get; set; }

        private string path { get; set; } = AppDomain.CurrentDomain.BaseDirectory.Replace("\\", "/") + "Assets/Adb/";

        private List<string> devices = new();
        public AdbHelper(string remoteIP, string port)
        {
            if (!Directory.Exists(this.path + "/Screenshot"))
            {
                Directory.CreateDirectory(this.path + "/Screenshot");
            }
            if (!string.IsNullOrEmpty(remoteIP) && !string.IsNullOrEmpty(port))
            {
                int.TryParse(port, out var _port);
                if (_port > 0)
                {
                    PipeCmd($"connect { remoteIP }:{_port}");
                }
            }
        }


        public void Dispose() { }

        public async void GetScreenAndLayout(int index)
        {
            await Task.Run(() =>
             {
                 var name = "Screen_" + DateTime.Now.ToString("yy-MM-dd-HH-mm-ss") + ".png";
                 var fullName = this.path + "Screenshot/" + name;
                 PipeCmd($"-s { this.devices[index] } exec-out screencap -p > { fullName }");
                 for (var i = 0; i < 50; i++)
                 {
                     if (File.Exists(fullName))
                     {
                         break;
                     }
                     Thread.Sleep(100);
                 }
                 try
                 {
                     FileStream stream = new(fullName, FileMode.Open, FileAccess.Read);
                     var bitmap = new Bitmap(stream);
                     stream.Dispose();


                     var dumpStr = PipeCmd($"-s { this.devices[index] } exec-out uiautomator dump /dev/tty");
                     var startNum = dumpStr.IndexOf("<node");
                     var len = dumpStr.LastIndexOf("</node>") + 7 - startNum;
                     var xml = dumpStr.Substring(startNum, len);

                     var layout = new MoveCategory
                     {
                         ClassName = "RootLayout"
                     };

                     var doc = XDocument.Parse(xml);
                     ParseElements(doc.Elements(), ref layout);

                     this.Action?.Invoke(bitmap, layout);

                 }
                 catch (Exception e)
                 {
                     throw new Exception(e.Message);
                 }
             });
        }

        public Task<ObservableCollection<string>> List()
        {
            return Task.Run(() =>
            {
                var result = new ObservableCollection<string>();
                this.devices.Clear();
                var output = PipeCmd("devices");
                var array = output.Substring(output.IndexOf("List of devices attached") + 16).Split("\r\n");
                for (var i = 0; i < array.Length; i++)
                {
                    var devices = array[i].Split("dev");
                    if (devices.Length == 2)
                    {
                        result.Add(devices[0].Trim());
                        this.devices.Add(devices[0].Trim());
                    }
                }
                return result;
            });
        }

        public string PipeCmd(string theCommand)
        {
            var command = $"/C { this.path }adb.exe { theCommand }";
            ProcessStartInfo start = new("cmd.exe")
            {
                Arguments = command,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardInput = true,
                UseShellExecute = false,
                StandardOutputEncoding = System.Text.Encoding.UTF8
            };

            var pipe = Process.Start(start);
            Debug.Assert(pipe != null);
            var readStream = pipe.StandardOutput;
            var OutputStr = readStream.ReadToEnd();
            pipe.WaitForExit(10000);
            pipe.Close();
            readStream.Close();
            return OutputStr;
        }

        public static void ParseElements(IEnumerable<XElement> elements, ref MoveCategory root)
        {
            foreach (var element in elements)
            {
                var node = ParseElement(element);
                if (element.HasElements)
                {
                    ParseElements(element.Elements(), ref node);
                }
                if (root.Children == null)
                {
                    root.Children = new ObservableCollection<MoveCategory>();
                }
                root.Children.Add(node);
            }

        }

        public static MoveCategory ParseElement(XElement element)
        {
            var result = new MoveCategory();
            foreach (var attr in element.Attributes())
            {
                switch (attr.Name.ToString())
                {
                    case "index":
                        result.IndexInParent = int.Parse(attr.Value); break;
                    case "text":
                        result.Text = attr.Value; break;
                    case "resource-id":
                        result.Id = attr.Value; break;
                    case "class":
                        result.ClassName = attr.Value; break;
                    case "package":
                        result.PackageName = attr.Value; break;
                    case "content-desc":
                        result.Desc = attr.Value; break;
                    case "checkable":
                        result.Checkable = bool.Parse(attr.Value); break;
                    case "checked":
                        result.Checked = bool.Parse(attr.Value); break;
                    case "clickable":
                        result.Clickable = bool.Parse(attr.Value); break;
                    case "enabled":
                        result.Enabled = bool.Parse(attr.Value); break;
                    case "focused":
                        result.Focused = bool.Parse(attr.Value); break;
                    case "scrollable":
                        result.Scrollable = bool.Parse(attr.Value); break;
                    case "long-clickable":
                    case "long-Clickable":
                        result.LongClickable = bool.Parse(attr.Value); break;
                    case "selected":
                        result.Selected = bool.Parse(attr.Value); break;
                    case "bounds":
                        result.Bounds = Bounds.Parse(attr.Value); break;

                    default:
                        break;
                }
            }
            return result;
        }
    }
}
